package org.matchergen;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.PropertyUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

public class PropertyMatcher<T, S> extends TypeSafeMatcher<T> {
	private Matcher<? super S> myMatcher;
	private String propertyName;

	public PropertyMatcher(String propertyName, Matcher<? super S> matchers) {
		this.myMatcher = matchers;
		this.propertyName = propertyName;
	}

	public void describeTo(Description description) {
		description.appendText(String.format("'%s' ", propertyName));
		description.appendDescriptionOf(myMatcher);
	}

	protected void describeMismatchSafely(T item, Description mismatchDescription) {
		try {
			S value = getValue(item);

			mismatchDescription.appendText(String.format("'%s' was ", propertyName));
			mismatchDescription.appendValue(value);
		} catch (IllegalAccessException e) {
			mismatchDescription.appendText(String.format("'%s' ist not accessable", propertyName));
		} catch (InvocationTargetException e) {
			mismatchDescription.appendText(String.format("getter for '%s' threw an exception", propertyName));
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			mismatchDescription.appendText(String.format("'%s' has no getter", propertyName));
		} catch (NoSuchFieldException e) {
			mismatchDescription.appendText(String.format("field '%s' does not exist", propertyName));
		}
	};

	@Override
	protected boolean matchesSafely(T item) {
		try {
			S value = getValue(item);

			return myMatcher.matches(value);
		} catch (Exception e) {
			return false;
		}
	}

	@SuppressWarnings("unchecked")
	protected S getValue(T item) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException,
			SecurityException, NoSuchFieldException {
		S value;

		// wenn Getter da
		if (PropertyUtils.isReadable(item, propertyName)) {
			value = (S) PropertyUtils.getProperty(item, propertyName);
		}
		// sonst versuchen Feld zu lesen
		else {
			Field f = item.getClass().getField(propertyName);

			value = (S) f.get(item);
		}

		return value;
	}
}